﻿using PlutoStudio.Diagnostics;
using PlutoStudio.Net.Messages;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;

namespace PlutoStudio.Net.Tcp
{
    public class TcpClient : INetClient
    {
        private static byte[] inOptionValues;

        private static TraceHelper trace = new TraceHelper("PlutoStudio.Net.Trace", "Default Trace.");
        private static ConcurrentBag<byte[]> bufferPool;
        private static ConcurrentBag<SocketAsyncEventArgs> socketEventArgsPool;
        private Socket socket;
        private byte[] buffer;
        public static int BufferSize { get; set; }
        private SocketAsyncEventArgs receiveArgs;
        private Messages.MessageQueue messageQueue;
        public bool IsConnected { get; private set; }

        public event ReceivedEventHandler Received;
        public event ConnectedEventHandler Connected;
        public event SendedEventHandler Sended;
        public event ClosedEventHandler Closed;
        public event ErrorEventHandler Error;

        public EndPoint RemoteEndPoint { get; private set; }

        public EndPoint LocalEndPoint { get; private set; }

        static TcpClient()
        {
            bufferPool = new ConcurrentBag<byte[]>();
            socketEventArgsPool = new ConcurrentBag<SocketAsyncEventArgs>();
            uint dummy = 0;
            inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
            BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);
            BitConverter.GetBytes((uint)1000).CopyTo(inOptionValues, Marshal.SizeOf(dummy));
            BitConverter.GetBytes((uint)1000).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);
        }

        private static byte[] GetBuffer()
        {
            byte[] result;
            if (!bufferPool.TryTake(out result)|| result==null)
            {
                result = new byte[BufferSize];
            }
            return result;
        }

        private static void ReleaseBuffer(byte[] buffer)
        {
            if (buffer!=null)
            {
                Array.Clear(buffer, 0, BufferSize);
                bufferPool.Add(buffer);
            }
        }

        private static SocketAsyncEventArgs GetSocketAsyncEventArgs()
        {
            SocketAsyncEventArgs result;
            if (!socketEventArgsPool.TryTake(out result) || result==null )
            {
                result = new SocketAsyncEventArgs();
            }
            return result;
        }

        private static void ReleaseSocketAsyncEventArgs(SocketAsyncEventArgs args)
        {
            try
            {
                args.UserToken = null;
                args.SetBuffer(null, 0, 0);
                socketEventArgsPool.Add(args);
            }
            catch (Exception ex)
            {
                trace.TraceError("释放Socket事件对象失败：{0}", ex);
            }
        }

        public TcpClient()
        {
            BufferSize = Configuration.ConfigurationManager.Client.BufferSize;
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            Init();
        }

        internal TcpClient(Socket socket)
        {
            BufferSize = Configuration.ConfigurationManager.Listener.BufferSize;
            LocalEndPoint = socket.LocalEndPoint;
            RemoteEndPoint = socket.RemoteEndPoint;
            this.socket = socket;
            IsConnected = true;
            Init();
        }

        private void Init()
        {
            receiveArgs = GetSocketAsyncEventArgs();
            messageQueue = new Messages.MessageQueue();
            buffer = GetBuffer();
            receiveArgs.SetBuffer(buffer, 0, BufferSize);
            receiveArgs.Completed += ReceiveCompleted;
            try
            {
                socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
            }
            catch
            {
                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, inOptionValues);
            }
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
        }

        public void Connect()
        {
            Connect(Configuration.ConfigurationManager.Client.IPAddress, Configuration.ConfigurationManager.Client.Port);
        }

        public void Connect(IPAddress address, int port)
        {
            Connect(new IPEndPoint(address, port));
        }

        public void Connect(IPEndPoint remoteEndPoint)
        {
            if (IsConnected)
            {
                return;
            }
            var args = new SocketAsyncEventArgs();
            this.RemoteEndPoint = remoteEndPoint;
            args.RemoteEndPoint = remoteEndPoint;
            args.Completed += (o, e) =>
                {
                    ProcessConnected(e);
                };
            if (!socket.ConnectAsync(args))
            {
                ProcessConnected(args);
            }
        }

        private void ProcessConnected(SocketAsyncEventArgs e)
        {
            try
            {
                if (e.SocketError == SocketError.Success)
                {
                    LocalEndPoint = socket.LocalEndPoint;
                    OnConnected();
                }
                else
                {
                    trace.TraceError("连接到服务器{0}发生错误：{1}", e.RemoteEndPoint, e.SocketError);
                    OnError(new SocketException((int)e.SocketError));
                }
            }
            catch (SocketException ex)
            {
                trace.TraceError("连接到服务器引发异常:{0}", ex);
                OnError(ex);
            }
        }

        private void OnError(Exception ex)
        {
            var e = new ErrorEventArgs(ex);
            OnError(e);
        }

        protected virtual void OnError(ErrorEventArgs e)
        {
            if (Error != null)
            {
                Error(this, e);
            }
        }

        private void OnConnected()
        {
            IsConnected = true;
            var e = new EventArgs();
            OnConnected(e);
        }

        protected virtual void OnConnected(EventArgs e)
        {
            if (Connected != null)
            {
                Connected(this, e);
            }
        }

        public void BeginReceive()
        {
            try
            {
                bool willRaiseEvent = socket.ReceiveAsync(receiveArgs);
                if (!willRaiseEvent)
                {
                    ProcessReceive(receiveArgs);
                }
            }
            catch (Exception ex)
            {
                trace.TraceError("接收数据发生错误：{0}", ex);
                Close();
            }

        }

        void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
        {
            ProcessReceive(e);
        }

        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {

                trace.TraceInformation("接受到来自{0}的{1}字节的数据", RemoteEndPoint, e.BytesTransferred);
                messageQueue.Write(e.Buffer, e.Offset, e.BytesTransferred);
                Message message;
                while ((message = messageQueue.Read()) != null)
                {
                    var msg = message;
                    System.Threading.Tasks.Task.Factory.StartNew(() => OnReceived(RemoteEndPoint, msg));
                }
                BeginReceive();
            }
            else
            {
                trace.TraceError("接收来自{0}的数据发生错误，连接将被关闭：{1}", RemoteEndPoint, e.SocketError);
                Close();
            }
        }

        private void OnReceived(EndPoint remoteEP, Messages.Message message)
        {
            var e = new ReceivedEventArgs(remoteEP, message);
            OnReceived(e);
        }

        protected virtual void OnReceived(ReceivedEventArgs e)
        {
            if (Received != null)
            {
                Received(this, e);
            }
        }

        public void Send(Messages.Message message)
        {
            if (socket == null)
            {
                trace.TraceError("无法发送信息，连接被断开,消息：{0}", message);
                return;
            }
            trace.TraceInformation("发送信息到客户端 {0} ，消息：{1}", RemoteEndPoint, message);
            var buffer = message.ToBytes();
            var args = GetSocketAsyncEventArgs();
            args.SetBuffer(buffer, 0, buffer.Length);
            args.Completed += SendCompleted;
            try
            {
                if (!socket.SendAsync(args))
                {
                    ProcessSend(args);
                }
            }
            catch(Exception ex)
            {
                trace.TraceError("发送信息到客户端{0}失败：{1}",RemoteEndPoint, ex);
                Close();
            }
        }

        private void ProcessSend(SocketAsyncEventArgs e)
        {
            e.Completed -= SendCompleted;
            if (e.SocketError == SocketError.Success)
            {
                trace.TraceInformation("发送{0}字节的数据", e.BytesTransferred);
            }
            else
            {
                trace.TraceError("发送数据到{0}发生错误，连接将被关闭：{1}", RemoteEndPoint, e.SocketError);
                Close();
            }
            ReleaseSocketAsyncEventArgs(e);
            OnSended();
        }

        private void OnSended()
        {
            var e = EventArgs.Empty;
            OnSended(e);
        }

        protected virtual void OnSended(EventArgs e)
        {
            if (Sended != null)
            {
                Sended(this, e);
            }
        }

        void SendCompleted(object sender, SocketAsyncEventArgs e)
        {
            ProcessSend(e);
        }

        public void Close()
        {
            try
            {
                socket.Shutdown(SocketShutdown.Send);
                Trace.TraceInformation("连接已经被关闭！");
            }
            catch (ObjectDisposedException)
            {
                return;
            }
            catch (Exception ex)
            {
                trace.TraceError("关闭Socket连接发生异常！\r\n   {0}", ex);
            }
            socket.Close();
            if (receiveArgs != null)
            {
                receiveArgs.Completed -= ReceiveCompleted;
                ReleaseSocketAsyncEventArgs(receiveArgs);
                ReleaseBuffer(buffer);
                receiveArgs = null;
            }
            buffer = null;
            OnClosed();
        }

        private object ClosedLockObject = new object();

        private void OnClosed()
        {
            lock (ClosedLockObject)
            {
                if (!IsConnected)
                {
                    return;
                }
                IsConnected = false;
                var e = EventArgs.Empty;
                OnClosed(e);
            }
        }

        protected virtual void OnClosed(EventArgs e)
        {
            if (Closed != null)
            {
                Closed(this, e);
            }
        }

        void INetClient.Send(EndPoint remoteEP, Messages.Message message)
        {
            Send(message);
        }
    }
}